﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Krile.Common.Suggestor
{
    internal class SuggestableTextBoxCore : RichTextBox
    {
        float dpiX, dpiY;
        SuggestableTextBox parent;
        public SuggestableTextBoxCore(SuggestableTextBox parent)
            :base()
        {
            this.parent = parent;
            using (Graphics g = this.CreateGraphics())
            {
                dpiX = g.DpiX;
                dpiY = g.DpiY;
            }
            this.BorderStyle = System.Windows.Forms.BorderStyle.None;
        }

        internal int OverflowLength { get { return parent.TextMaxLimitLength; } }

        bool colored = false;
        // overflow coloring
        public void UpdateOverflowCheck()
        {
            this.SuspendLayout();
            this.Enabled = false;
            try
            {
                if (OverflowLength == -1)
                    return;
                int prevss = this.SelectionStart;
                if (this.TextLength > OverflowLength)
                {
                    colored = true;
                    this.SelectionStart = 0;
                    this.SelectionLength = OverflowLength;
                    this.SelectionBackColor = Color.White;
                    this.SelectionStart = OverflowLength;
                    this.SelectionLength = this.TextLength - OverflowLength;
                    this.SelectionBackColor = Color.LightCoral;
                }
                else
                {
                    if (!colored)
                        return;
                    colored = false;
                    this.SelectionStart = 0;
                    this.SelectionLength = this.TextLength;
                    this.SelectionBackColor = Color.White;
                }
                this.SelectionStart = prevss;
                this.SelectionLength = 0;
            }
            finally
            {
                this.Enabled = true;
                this.ResumeLayout();
                this.Focus();
            }
        }

        protected virtual char[] SuggestCandidates
        {
            get { return parent.SuggestCandidates; }
        }

        protected virtual SuggestList GetList(char hitCandidate, int targetStart, Point showLocation)
        {
            return parent.GetList(hitCandidate, targetStart, showLocation);
        }

        protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
        {
            OverridedPreKeyAction(e);
            base.OnPreviewKeyDown(e);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            OverridedKeyAction(e);
            base.OnKeyDown(e);
        }

        protected override void OnTextChanged(EventArgs e)
        {
            Process(this.Text, this.SelectionStart);
            UpdateOverflowCheck();
            base.OnTextChanged(e);
        }

        protected override void OnLinkClicked(LinkClickedEventArgs e)
        {
            Kernel.Bridge.CommonOperation.CallBrowser(e.LinkText);
        }

        #region Suggest system method

        SuggestList sug = null;
        int targetStart = -1;
        char prevHit = '\0';

        private void Process(string text, int selectedStart)
        {
            if (sug == null)
            {
                ProcessStartSuggest(text, selectedStart);
            }
            else
            {
                if (text.Length < targetStart || text[targetStart - 1] != prevHit)
                {
                    sug.SuggestClose();
                    sug = null;
                    ProcessStartSuggest(text, selectedStart);
                }
                else if (selectedStart < targetStart)
                {
                    sug.SuggestClose();
                    sug = null;
                }
                else
                {
                    var substr = text.Substring(targetStart);
                    int cutidx = 0;
                    while ((cutidx = substr.IndexOfAny(new[] { ' ', '\t', '\r', '\n' })) > -1)
                        substr = substr.Substring(0, cutidx);
                    sug.UpdateSearchTargets(substr);
                }
            }
        }

        private void ProcessStartSuggest(string text, int selectedStart)
        {
            if (String.IsNullOrEmpty(text) || selectedStart > text.Length || selectedStart < 1 || SuggestCandidates == null)
                return;
            // if not space, return
            if (selectedStart < text.Length && !" \t\r\n.".Contains(text[selectedStart]))
                return;
            foreach (var c in SuggestCandidates)
            {
                if (text[selectedStart - 1] == c)
                {
                    targetStart = selectedStart;
                    //Start suggest
                    Point displayOffset = this.GetPositionFromCharIndex(selectedStart);
                    //Get line height
                    int curLine = this.GetLineFromCharIndex(selectedStart);
                    float lineHeight = 0.0f;
                    switch (this.Font.Unit)
                    {
                        case GraphicsUnit.Display:
                        case GraphicsUnit.Pixel:
                            lineHeight = this.Font.Size;
                            break;
                        case GraphicsUnit.Document:
                            lineHeight = this.Font.Size / 300.0f * dpiY;
                            break;
                        case GraphicsUnit.Inch:
                            lineHeight = this.Font.Size * dpiY;
                            break;
                        case GraphicsUnit.Point:
                            lineHeight = this.Font.Size / 72.0f * dpiY;
                            break;
                        default:
                            throw new NotSupportedException("Unit " + this.Font.Unit.ToString() + " is not supported.");
                    }
                    //set display offset
                    displayOffset.Y += (int)Math.Ceiling(lineHeight);
                    sug = GetList(c, targetStart, this.PointToScreen(displayOffset));
                    if (sug == null)
                        return;

                    sug.OnApplied += new Action(sug_OnApplied);
                    
                    //親コントロールを探す
                    Control parent = this.Parent;
                    Form pf = null;
                    
                    while (parent != null && (pf = parent as Form) == null)
                        parent = parent.Parent;

                    if (pf != null)
                        pf.AddOwnedForm(sug);

                    sug.Show();
                    this.Focus();
                    prevHit = c;
                    break;
                }
            }
        }

        void sug_OnApplied()
        {
            string near = null;
            if ((near = sug.GetNearestCandidate()) != null)
            {
                var substr = this.Text.Substring(targetStart);
                int cutidx = 0;
                while ((cutidx = substr.IndexOf(' ')) > -1)
                    substr = substr.Substring(0, cutidx);
                this.SelectionStart = targetStart;
                this.SelectionLength = substr.Length;
                this.SelectedText = near;
                this.SelectionStart = targetStart + near.Length;

                sug.SuggestClose();
                sug = null;
            }
        }

        private void OverridedPreKeyAction(PreviewKeyDownEventArgs e)
        {
            if (sug == null) return;
            switch (e.KeyCode)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                    e.IsInputKey = true;
                    break;
            }
            if (e.KeyCode == Keys.Left && this.SelectionStart == targetStart)
            {
                sug.SuggestClose();
                sug = null;
            }
        }

        private void OverridedKeyAction(KeyEventArgs e)
        {
            if (sug == null) return;
            string near = null;
            if (e.KeyCode == Keys.Up)
            {
                sug.MoveSelectionIndex(true);
            }
            else if (e.KeyCode == Keys.Down)
            {
                sug.MoveSelectionIndex(false);
            }
            else if ((e.KeyCode == Keys.Enter || e.KeyCode == Keys.Space) && (near = sug.GetNearestCandidate()) != null)
            {
                if (e.KeyCode == Keys.Enter)
                {
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                }

                var substr = this.Text.Substring(targetStart);
                int cutidx = 0;
                while ((cutidx = substr.IndexOf(' ')) > -1)
                    substr = substr.Substring(0, cutidx);
                this.SelectionStart = targetStart;
                this.SelectionLength = substr.Length;
                this.SelectedText = near;
                this.SelectionStart = targetStart + near.Length;

                sug.SuggestClose();
                sug = null;
            }
            else if (e.KeyCode == Keys.Escape || e.KeyCode == Keys.Enter || e.KeyCode == Keys.Space)
            {
                if (e.KeyCode != Keys.Space)
                {
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                }

                sug.SuggestClose();
                sug = null;
            }
        }

        #endregion
    }
}
